﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Security.Principal;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using System.Web.Hosting;
using System.Xml;
using LynxWiki.DomainModel;
using LynxWiki.Search;


namespace LynxWiki.Repository
{
    public class AttachmentRepository
    {
        //these values should all be in web.config
        private int maxBytes = Int32.Parse(LynxWiki.DomainModel.WikiRoot.MaxAttachmentSize);

        private static string imageRegexString = @"\.(?:jpeg|jpg|gif|png|webp|bmp|psd|pspimage|thm|tif|tiff|yuv|ai|eps|ps|svg)$";
        private static Regex imageRegex = new Regex(imageRegexString, RegexOptions.Singleline | RegexOptions.Compiled);

        private static string videoRegexString = @"\.(?:mpeg|mpg|mp4|h264|mov|wmv|vp8|vp9|ogg)$";
        private static Regex videoRegex = new Regex(videoRegexString, RegexOptions.Singleline | RegexOptions.Compiled);

        private static string docRegexString = @"\.(?:doc|docx|txt|text|xls|log|rtf|pages|odt|msg|wpd|pdf|csv|dat|mdb|accdb|pps|ppt|pptx)$";
        private static Regex docRegex = new Regex(docRegexString, RegexOptions.Singleline | RegexOptions.Compiled);

        private static string badRegexString = @"\.(?:exe|bat|vb|cgi|com|pif|jar|gadget|wsf|swf)$";
        private static Regex badRegex = new Regex(badRegexString, RegexOptions.Singleline | RegexOptions.Compiled);

        private string[] fileTypes = { "images", "docs", "videos", "misc" };

        private AttachmentVersion _attachmentVersion;

        string fileType;
        string fileName;
        string path;

        public AttachmentRepository() { }

        public async Task<string> UploadFile(HttpPostedFileBase file, string versNote, string tags, string allowUpdateUser = "", 
                    string allowUpdateRole = "", string denyReadUser = "", 
                    string denyReadRole = "", string creator = "", string nameSpace = "")
        {
            _attachmentVersion = new AttachmentVersion();
            AttachmentVersion oldVersion;
            if (file.ContentLength > 0 && file.ContentLength <= maxBytes) {
                fileType = string.Empty;
                if (badRegex.IsMatch(file.FileName)) {
                    return  string.Format("Error - This file type is not allowed; file: {0}", file.FileName);
                }
                _attachmentVersion.FileType = DetermineFileType(file.FileName);
                try {
                    fileName = Path.GetFileName(file.FileName);
                    _attachmentVersion.AttachmentName = fileName;
                    path = Path.Combine(HttpContext.Current.Server.MapPath("~/Attachments/upload"),nameSpace,  _attachmentVersion.FileType, fileName);
                    int version = GetAttachmentCurrentVersionNumber(fileName, path) + 1;
                    if (version > 1) {
                        oldVersion = new AttachmentVersion(File.ReadAllText(path + "__current.xml"));
                        if (oldVersion != null) {
                            if (!oldVersion.Updatable(HttpContext.Current.User)) {
                                if (oldVersion.CheckOutUser != creator) {
                                    return string.Format("Error - File: {0} checked out on {1} by {2}", fileName, oldVersion.CheckedOutOn, oldVersion.CheckOutUser);
                                }
                                else {
                                    oldVersion.CheckIn();
                                    File.WriteAllText(path, oldVersion.ToXml(), Encoding.UTF8);
                                }
                            }
                        }
                    }
                    file.SaveAs(path);
                    string versPath = Path.Combine(HttpContext.Current.Server.MapPath("~/Attachments/upload"), nameSpace, _attachmentVersion.FileType, fileName.Substring(0, fileName.LastIndexOf(".")) + "__v" + version.ToString("0000") + fileName.Substring(fileName.LastIndexOf(".")));
                    file.SaveAs(versPath);
                    _attachmentVersion.FileUploadResult = string.Format("Success - File: {0} with {1} bytes upload completed.", file.FileName, file.ContentLength);
                    AttachmentVersion av = new AttachmentVersion(fileName, path, creator, version, _attachmentVersion.FileType, denyReadUser, denyReadRole, allowUpdateUser, allowUpdateRole, tags, versNote, nameSpace);
                    await av.GetAttachmentText();
                    string current = path + "__current.xml";
                    File.WriteAllText(current, av.ToXml(), Encoding.UTF8);
                    string vers = path + "__v" + version.ToString("0000") + "_" + DateTime.Now.ToString("yyyy-MM-ddTHH-mm-ss-fff") + ".xml";
                    File.WriteAllText(vers, av.ToXml(), Encoding.UTF8);

                    AttachmentSearch.AddUpdateLuceneIndex(new DomainModel.AttSearchData(av));

                    return _attachmentVersion.FileUploadResult;
                }
                catch (Exception ex) {
                    return string.Format("Error uploading file: {0}\r\n\r\n{1}", file.FileName, ex.Message);
                }
            }
            else if (file.ContentLength == 0) {
                return String.Format("Error - File: {0} has zero (0) bytes", file.FileName);
            }
            else if (file.ContentLength > maxBytes) {
                return string.Format("Error - File: {0} is exceeds {1} bytes allowed. It has {2} bytes.", file.FileName, maxBytes, file.ContentLength);
            }
            return "Error - Unspecified error in file upload";
        }

        public void CheckOutAttachment(string attachPath, string fileName, string userName)
        {
            int version = 0;
            string svrPath = HttpContext.Current.Server.MapPath("~/" + attachPath);
            AttachmentVersion curVers = new AttachmentVersion(File.ReadAllText(Path.Combine(svrPath, fileName  + "__current.xml")));
            if (curVers != null) {
                if (curVers.Updatable(HttpContext.Current.User)) {
                    curVers.CheckOutUser = userName;
                    curVers.CheckedOutOn = DateTime.Now;
                    File.WriteAllText(curVers.AttachmentDir + "__current.xml", curVers.ToXml(), Encoding.UTF8);
                }
                version = curVers.Version;
                DirectoryInfo dir = new DirectoryInfo(svrPath);
                FileInfo fv = (from FileInfo file in dir.GetFiles()
                               where file.Name.Contains(fileName + "__v" + version.ToString("0000"))
                               select file).FirstOrDefault();
                if (fv != null) {
                    File.WriteAllText(fv.FullName, curVers.ToXml(), Encoding.UTF8);
                }
            }
        }

        public void CheckInAttachment(string attachPath, string fileName)
        {
            int version = 0;
            string svrPath = HttpContext.Current.Server.MapPath("~/" + attachPath);
            AttachmentVersion curVers = new AttachmentVersion(File.ReadAllText(Path.Combine(svrPath, fileName + "__current.xml")));
            if (curVers != null) {
                curVers.CheckedInOn = DateTime.Now;
                File.WriteAllText(curVers.AttachmentDir + "__current.xml", curVers.ToXml(), Encoding.UTF8);
                version = curVers.Version;
                DirectoryInfo dir = new DirectoryInfo(svrPath);
                FileInfo fv = (from FileInfo file in dir.GetFiles()
                               where file.Name.Contains(fileName + "__v" + version.ToString("0000"))
                               select file).FirstOrDefault();
                if (fv != null) {
                    File.WriteAllText(fv.FullName, curVers.ToXml(), Encoding.UTF8);
                }
            }
        }

        public int GetAttachmentCurrentVersionNumber(string attachmentName, string path)
        {
            int version = 0;
            FileInfo cvFile = new FileInfo(path);
            if (cvFile.Exists) {
                FileInfo cvf = (from FileInfo file in cvFile.Directory.GetFiles()
                                where file.Name == (attachmentName + "__current.xml")
                                orderby file.CreationTime descending
                                select file).FirstOrDefault();
                if (cvf != null) {
                    AttachmentVersion av = new AttachmentVersion(File.ReadAllText(cvf.FullName));
                    version = av.Version;
                }
            }
            return version;
        }

        public bool GetCurrentVersionUpdatable(string attachName, string nsName, IPrincipal user)
        {
            AttachmentVersion cv = GetCurrentVersion(attachName, nsName);
            if (cv != null) {
                return cv.Updatable(user);
            }
            return false;
        }

        public string GetCurrentVersionCheckedOutUser(string attachName, string nsName)
        {
            string user = string.Empty;
            AttachmentVersion cv = GetCurrentVersion(attachName, nsName);
            if (cv != null) {
                user = cv.CheckOutUser;
            }
            return user;
        }

        public bool GetCurrentVersionReadAccess(string attachName, string nsName, IPrincipal user)
        {
            AttachmentVersion cv = GetCurrentVersion(attachName, nsName);
            if (cv != null) {
                return cv.UserReadAccess(user);
            }
            return false;
        }

        public AttachmentVersion GetCurrentVersion(string attachName, string nsName)
        {
            AttachmentVersion av = new AttachmentVersion();
            if (nsName == "docs" || nsName == "images" || nsName == "misc" || nsName == "videso") {
                return null;
            }
            else {
                av.FileType = DetermineFileType(attachName);
                path = Path.Combine(HttpContext.Current.Server.MapPath("~/Attachments/upload"), nsName, av.FileType, attachName + "__current.xml");
            }
            FileInfo cvf = new FileInfo(path);
            if (cvf.Exists) {
                return new AttachmentVersion(File.ReadAllText(path));
            }
            else return null;
        }

        public List<AttachmentVersion> GetVersionList(string attachName, string nsName)
        {
            List<AttachmentVersion> avl = new List<AttachmentVersion>();
            string fileType = DetermineFileType(attachName);
            DirectoryInfo dir = new DirectoryInfo(Path.Combine(HttpContext.Current.Server.MapPath("~/Attachments/upload"), nsName, fileType));
            if (dir.Exists) {
                List<FileInfo> lfi = (from FileInfo file in dir.GetFiles() where file.Name.Contains(attachName + "__v") orderby file.CreationTime descending select file).ToList();
                foreach (FileInfo file in lfi) {
                    AttachmentVersion av = new AttachmentVersion(File.ReadAllText(file.FullName));
                    avl.Add(av);
                }
            }
            return avl;
        }

        private string DetermineFileType(string attachName)
        {          
            string fileType;
            if (imageRegex.IsMatch(attachName)) fileType = "images";
            else if (docRegex.IsMatch(attachName)) fileType = "docs";
            else if (videoRegex.IsMatch(attachName)) fileType = "videos";
            else fileType = "misc";
            return fileType;
        }

        public List<FileInfo> ListAttachmentFiles(int fileType, string nsName)
        {
            if (fileType == 5) {
                List<FileInfo> files = ListAttachmentFiles(fileTypes[0], nsName);
                files.AddRange(ListAttachmentFiles(fileTypes[1], nsName));
                files.AddRange(ListAttachmentFiles(fileTypes[2], nsName));
                files.AddRange(ListAttachmentFiles(fileTypes[3], nsName));
                return files;
            }
            else if (fileType > 0 && fileType < 5) {
                return ListAttachmentFiles(fileTypes[fileType - 1], nsName);
            }
            return null;
        }

        public FileInfo ListAttachmentFile(string fName, string attachPath)
        {
            string svrPath = HttpContext.Current.Server.MapPath("~/" + attachPath);
            FileInfo lfi = null;
            DirectoryInfo dir = new DirectoryInfo(svrPath);
            if (dir.Exists) {
                lfi = (from FileInfo file in dir.GetFiles() where file.Name == fName select file).FirstOrDefault();

            }
            return lfi;
        }

        public List<FileInfo> ListAttachmentFiles(string fileType, string nsName)
        {
            
            string path = Path.Combine(HttpContext.Current.Server.MapPath("~/Attachments/upload"), nsName, fileType);
            DirectoryInfo dir = new DirectoryInfo(path);
            if (dir.Exists) {
                List<FileInfo> files = new List<FileInfo>();
                List<FileInfo> lfi = (from FileInfo file in dir.GetFiles() where file.Name.Contains("__current.xml") select file).ToList();

                foreach (FileInfo file in lfi) {
                    AttachmentVersion av = new AttachmentVersion(File.ReadAllText(file.FullName));
                    files.Add(new FileInfo(Path.Combine(path, av.AttachmentName)));
                }
                return files;
            }
            return null;
        }

        public string DeleteAttachmentFile(string fileName, string path)
        {
            string fPath = Path.Combine(HttpContext.Current.Server.MapPath("~/" + path));
            if (string.IsNullOrEmpty(fileName)) {
                return "Error - File name not specified for DeleteAttachment File";
            }
            if (string.IsNullOrEmpty(path)) {
                return "Error - File path not specified for DeleteAttachment File";
            }
            if (!fPath.Contains("Attachments\\upload")) {
                return "Error - path is outside of upload directories";
            }
            try {
                FileInfo file = new FileInfo(Path.Combine(fPath, fileName));
                if (file.Exists) {
                    List<FileInfo> lfi = (from FileInfo test in file.Directory.GetFiles() where file.Name.Contains(file.Name.Substring(0, file.Name.LastIndexOf("."))) select test).ToList();
                    foreach (FileInfo dFile in lfi) {
                        dFile.Delete();
                    }
                    return string.Format("Success - File: {0} deleted.", fileName);
                }
                return string.Format("Error - File: {0} does not exist to delete.", fileName);
            }
            catch (Exception ex) {
                return string.Format("Error uploading file: {0}\r\n\r\n{1}", fileName, ex.Message);
            }
        }


    }
}
